﻿using System;
using System.Collections.Generic;
using System.Configuration.Provider;
using System.Linq;
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;
using System.Web.Security;
using Coslen.Framework.Helpers;
using Coslen.TahorCRM.Domain.Model.Entities;
using Coslen.TahorCRM.Domain.Repositories.Concretes;
using Coslen.TahorCRM.Domain.Repositories.Interfaces;
using Ninject;

namespace Coslen.TahorCRM.Security
{
    public class TahorMembershipProvider : MembershipProvider
    {
        private const int SALT_SIZE = 64;

        [Inject]
        public IUserRepository UserRepository;

        public TahorMembershipProvider()
            : this(new UserRepository())
        {
        }

        public TahorMembershipProvider(UserRepository userRepository)
        {
            UserRepository = userRepository;
        }

        //
        // Properties from web.config, default all to False
        //
        private string _applicationName;
        private bool _enablePasswordReset;
        private bool _enablePasswordRetrieval = false;
        private bool _requiresQuestionAndAnswer = false;
        private bool _requiresUniqueEmail = true;
        private int _maxInvalidPasswordAttempts;
        private int _passwordAttemptWindow;
        private int _minRequiredPasswordLength;
        private int _minRequiredNonalphanumericCharacters;
        private string _passwordStrengthRegularExpression;
        private MembershipPasswordFormat _passwordFormat = MembershipPasswordFormat.Hashed;

        


        /// <summary>
        /// This will read all the values from the web config
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (string.IsNullOrWhiteSpace(name))
            {
                name = "TahorMembershipProvider";
            }

            if (String.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "Tahor CRM Membership Provider");
            }

            base.Initialize(name, config);

            _applicationName = GetConfigValue(config["applicationName"], "/");
            _maxInvalidPasswordAttempts = Convert.ToInt32(GetConfigValue(config["maxInvalidPasswordAttempts"], "5"));
            _passwordAttemptWindow = Convert.ToInt32(GetConfigValue(config["passwordAttemptWindow"], "10"));
            _minRequiredNonalphanumericCharacters = Convert.ToInt32(GetConfigValue(config["minRequiredNonalphanumericCharacters"], "1"));
            _minRequiredPasswordLength = Convert.ToInt32(GetConfigValue(config["minRequiredPasswordLength"], "6"));
            _enablePasswordReset = Convert.ToBoolean(GetConfigValue(config["enablePasswordReset"], "true"));
            _passwordStrengthRegularExpression = Convert.ToString(GetConfigValue(config["passwordStrengthRegularExpression"], ""));

        }


        #region From Web Config

        public override bool EnablePasswordRetrieval
        {
            get { return _enablePasswordRetrieval; }
        }

        public override bool EnablePasswordReset
        {
            get { return _enablePasswordReset; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return _requiresQuestionAndAnswer; }
        }

        public override string ApplicationName
        {
            get { return _applicationName; }
            set { _applicationName = value; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return _maxInvalidPasswordAttempts; }
        }

        public override int PasswordAttemptWindow
        {
            get { return _passwordAttemptWindow; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return _requiresUniqueEmail; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return _passwordFormat; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return _minRequiredPasswordLength; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return _minRequiredNonalphanumericCharacters; }
        }

        public override string PasswordStrengthRegularExpression
        {
            get { return _passwordStrengthRegularExpression; }
        }

        #endregion

        /// <summary>
        /// This is used to validate if the username and password combination is a valid
        /// against a user in the system
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public override bool ValidateUser(string username, string password)
        {
            // Early Exit...
            if (string.IsNullOrEmpty(password.Trim()))
            {
                return false;
            }

            
            string salt = GetSaltForUser(username);
            string hash = SecurityHelpers.EncryptPassword(password, salt);

            User user = UserRepository.GetUserByUserName(username);
            if (user == null)
            {
                return false;
            }

            if (!user.IsActivated)
            {
                return false;
            }

            if (user.IsLockedOut)
            {
                return false;
            }

            if (user.PasswordHash == hash)
            {
                return true;
            }

            return false;
        }

        
        public override MembershipUser CreateUser(string username, string password, string email,
                                                  string passwordQuestion, string passwordAnswer, bool isApproved,
                                                  object providerUserKey, out MembershipCreateStatus status)
        {
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username,
                                                                password,
                                                                true);

            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && GetUserNameByEmail(email) != "")
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            MembershipUser u = GetUser(username, false);

            // Random Salt to use
            string salt = GenerateSalt();

            if (u == null)
            {
                User user = new User();

                user.Username = username;
                user.Email = email;
                user.PasswordHash = SecurityHelpers.EncryptPassword(password, salt);
                user.PasswordSalt = salt;
                user.CreatedAt = DateTime.Now;
                user.UpdatedAt = DateTime.Now;
                user.IsActivated = false;
                user.IsLockedOut = false;
                user.LastLockedOutAt = DateTime.Now;
                user.LastLoginAt = DateTime.Now;
                user.ActivatedAt = DateTime.Now;

                UserRepository.Add(user);
                UserRepository.Save();

                status = MembershipCreateStatus.Success;

                return GetUser(username, false);
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
            }

            return null;
        }

        /// <summary>
        /// This will return the username for the user with the given email address.
        /// </summary>
        /// <param name="email"></param>
        /// <returns></returns>
        public override string GetUserNameByEmail(string email)
        {
            if (UserRepository.FindBy(x => x.Email == email).Count() != 0)
            {
                return UserRepository.FindBy(x => x.Email == email).First().Username;
            }

            return string.Empty;
        }


        /// <summary>
        /// This is used to retrieve a user based upon the username
        /// </summary>
        /// <param name="username"></param>
        /// <param name="userIsOnline"></param>
        /// <returns></returns>
        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            if (UserRepository.FindBy(x => x.Username == username).Count() != 0)
            {
                User dbuser = UserRepository.FindBy(x => x.Username == username).FirstOrDefault();

                string _username = dbuser.Username;
                int _providerUserKey = dbuser.UserId;
                string _email = dbuser.Email;
                string _passwordQuestion = "";
                string _comment = string.Empty;
                bool _isApproved = dbuser.IsActivated;
                bool _isLockedOut = dbuser.IsLockedOut;
                DateTime _creationDate = dbuser.CreatedAt;
                DateTime _lastLoginDate = dbuser.LastLoginAt.HasValue ? dbuser.LastLoginAt.Value : DateTime.MinValue;
                DateTime _lastActivityDate = DateTime.Now;
                DateTime _lastPasswordChangedDate = DateTime.Now;
                DateTime _lastLockedOutDate = dbuser.LastLockedOutAt.HasValue ? dbuser.LastLockedOutAt.Value : DateTime.MinValue;

                MembershipUser user = new MembershipUser("TahorMembershipProvider",
                                                          _username,
                                                          _providerUserKey,
                                                          _email,
                                                          _passwordQuestion,
                                                          _comment,
                                                          _isApproved,
                                                          _isLockedOut,
                                                          _creationDate,
                                                          _lastLoginDate,
                                                          _lastActivityDate,
                                                          _lastPasswordChangedDate,
                                                          _lastLockedOutDate);

                return user;
            }
            else
            {
                return null;
            }

        }

        #region Non Implemented Methods

        public override bool ChangePasswordQuestionAndAnswer(string username, string password,
                                                             string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize,
                                                                 out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize,
                                                                  out int totalRecords)
        {
            throw new NotImplementedException();
        }


        #endregion

        
        /// <summary>
        /// A helper function to retrieve config values from the configuration file. 
        /// </summary>
        /// <param name="configValue"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private string GetConfigValue(string configValue, string defaultValue)
        {
            if (string.IsNullOrEmpty(configValue))
            {
                return defaultValue;
            }

            return configValue;
        }


        /// <summary>
        /// Method: GetSaltForUser
        ///
        /// Description:
        /// Gets the current salt used to encrypt or hash the password, question, and answer
        /// </summary>
        private string GetSaltForUser(string username)
        {
            User dbuser = UserRepository.FindBy(x => x.Username == username).FirstOrDefault();

            if (dbuser == null)
            {
                throw new ApplicationException("Could not find user");
            }

            return dbuser.PasswordSalt;
        }

        /// <summary>
        /// Method: GenerateSalt
        ///
        /// Description:
        /// Generates a random salt string using a random number generator
        /// </summary>
        private string GenerateSalt()
        {
            var saltBytes = new byte[SALT_SIZE];
            new RNGCryptoServiceProvider().GetBytes(saltBytes);
            string salt = Convert.ToBase64String(saltBytes);
            return (salt.Length > 128) ? salt.Substring(0, 128) : salt;
        }

    }
}
